#include "../base/SampleBase.h"
#include "../base/D3DUtil.h"
#include "../base/Win32Helper.h"

class PickingSample : public SampleBase
{
private:
	ID3DXMesh*         m_teapot;
	ID3DXMesh*         m_sphere;
	D3DXMATRIX         m_world;
	d3dutil::BoundingSphere   m_bSphere;

public:
	d3dutil::Ray  CalcPickingRay(int x, int y)
	{
		float px = 0.0f;
		float py = 0.0f;

		D3DVIEWPORT9   viewport;
		m_device->GetViewport(&viewport);

		D3DXMATRIX     proj;
		m_device->GetTransform(D3DTS_PROJECTION, &proj);

		px = ((( 2.0f * x) / viewport.Width)  - 1.0f) / proj(0, 0);
		py = (((-2.0f * y) / viewport.Height) + 1.0f) / proj(1, 1);

		d3dutil::Ray ray;
		ray.m_origin    = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
		ray.m_direction = D3DXVECTOR3(px, py, 1.0f);

		return ray;
	}

	void TransformRay(d3dutil::Ray* ray, D3DXMATRIX* T)
	{
		// transform the ray's origin, w = 1
		D3DXVec3TransformCoord(
			&ray->m_origin,
			&ray->m_origin,
			T);

		// transform the ray's direction, w = 0
		D3DXVec3TransformNormal(
			&ray->m_direction,
			&ray->m_direction,
			T);

		// normalize
		D3DXVec3Normalize(&ray->m_direction, &ray->m_direction);
	}

	bool RaySphereIntTest(d3dutil::Ray* ray, d3dutil::BoundingSphere* sphere)
	{
		D3DXVECTOR3  v = ray->m_origin - sphere->m_center;

		float b = 2.0f * D3DXVec3Dot(&ray->m_direction, &v);
		float c = D3DXVec3Dot(&v, &v) - (sphere->m_radius * sphere->m_radius);

		// find the discriminant
		float discriminant = (b*b) - (4.0f *c);

		// test for imaginary number
		if (discriminant < 0.0f)
			return false;

		discriminant = sqrtf(discriminant);

		float s0 = (-b + discriminant) / 2.0f;
		float s1 = (-b + discriminant) / 2.0f;

		// if a solution is >= 0, then we intersected the sphere
		if (s0 > 0.0f || s1 >= 0.0f)
			return true;

		return false;
	}

	virtual bool Setup()
	{
		D3DXCreateTeapot(m_device, &m_teapot, 0);

		// Compute the bounding sphere
		BYTE* v = 0;
		m_teapot->LockVertexBuffer(0, (void**)&v);

		D3DXComputeBoundingSphere(
			(D3DXVECTOR3*)v,
			m_teapot->GetNumVertices(),
			D3DXGetFVFVertexSize(m_teapot->GetFVF()),
			&m_bSphere.m_center,
			&m_bSphere.m_radius);

		m_teapot->UnlockVertexBuffer();

		D3DXCreateSphere(m_device, m_bSphere.m_radius, 20, 20, &m_sphere, 0);

		// Set light
		D3DXVECTOR3  dir(0.707f, 0.0f, 0.707f);
		D3DXCOLOR    col(1.0f, 1.0f, 1.0f, 1.0f);
		D3DLIGHT9    light = d3dutil::InitDirectionalLight(&dir, &col);

		m_device->SetLight(0, &light);
		m_device->LightEnable(0, true);
		m_device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
		m_device->SetRenderState(D3DRS_SPECULARENABLE,   false);

		// Set view matrix
		D3DXVECTOR3  pos(0.0f, 0.0f, -10.f);
		D3DXVECTOR3  target(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3  up(0.0f, 1.0f, 0.0f);

		D3DXMATRIX  V;
		D3DXMatrixLookAtLH(&V, &pos, &target, &up);
		m_device->SetTransform(D3DTS_VIEW, &V);

		// Set Projection Matrix
		D3DXMATRIX  proj;
		D3DXMatrixPerspectiveFovLH(
			&proj,
			D3DX_PI  * 0.25f, 
			(float)m_windowWidth / (float) m_windowHeight,
			1.0f, 
			1000.0f);
		m_device->SetTransform(D3DTS_PROJECTION, &proj);

		return true;
	}

	virtual void Cleanup()
	{
		d3dutil::Release<ID3DXMesh*>(m_teapot);
		d3dutil::Release<ID3DXMesh*>(m_sphere);
	}

	bool Display(float timeDelta)
	{
		if(m_device)
		{
			// Update teapot
			static float r     = 0.0f;
			static float v     = 1.0f;
			static float angle = 0.0f;

			D3DXMatrixTranslation(&m_world, cosf(angle) * r, sinf(angle) * r, 10.0f);
			
			// Transform the bouding sphere to match the teapot positionin the world
			m_bSphere.m_center = D3DXVECTOR3(cosf(angle) * r, sinf(angle) * r, 10.0f);

			// transform the bouding sphere to match the teapots position in the world

			r += v * timeDelta;

			if(r > 8.0f)
				v = -v; // reverse direction

			if(r < 0.0f)
				v = -v; // reverse direction;

			angle += 1.0f * D3DX_PI * timeDelta;
			if (angle >= D3DX_PI * 2.0f)
				angle = 0.0f;

			m_device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
			m_device->BeginScene();

			m_device->SetTransform(D3DTS_WORLD, &m_world);
			m_device->SetMaterial(&d3dutil::YELLOW_MTRL);
			m_teapot->DrawSubset(0);
			
			// Render the bouding sphere with alpha blending so we
			// can see through it
			m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
			m_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			m_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

			D3DMATERIAL9  blue = d3dutil::BLUE_MTRL;
			blue.Diffuse.a = 0.25f;
			m_device->SetMaterial(&blue);
			m_sphere->DrawSubset(0);

			m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
			m_device->EndScene();
			m_device->Present(0, 0, 0, 0);
		}
		return true;
	}

	void _OnLeftMouseDown(HWND hwnd, UINT msg, WPARAM wPraram, LPARAM lParam)
	{
		d3dutil::Ray ray = CalcPickingRay(LOWORD(lParam), HIWORD(lParam));

		// transform the ray to world space
		D3DXMATRIX view;
		m_device->GetTransform(D3DTS_VIEW, &view);

		D3DXMATRIX viewInverse;
		D3DXMatrixInverse(&viewInverse, 0, &view);

		TransformRay(&ray, &viewInverse);

		// test for a hit
		if (RaySphereIntTest(&ray, &m_bSphere))
		{
			::MessageBox(0, "Hit!", "HIT", 0);
		}		
	}
};

SampleBase* sample = NULL;

void MyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	PickingSample* picking = dynamic_cast<PickingSample*>(sample);
	picking->_OnLeftMouseDown(hwnd, msg, wParam, lParam);
}

SampleBase* g_CreateSample()
{
	Win32Helper::s_userWndProc = MyWndProc;
	sample = new PickingSample;
	return sample;
}